package com.smsc.headend.task.engine.executor.impl.deviceconfig;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.smsc.headend.common.enums.errorcode.TaskExecuteErrorCode;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.dmo.MeterConfigDMO;
import com.smsc.headend.module.asset.dto.LoadLimitDataDto;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.common.entity.DeviceOperation;
import com.smsc.headend.module.common.entity.ScalerUnit;
import com.smsc.headend.module.data.dmo.DeviceSAPandKeys;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.enums.DictionaryType;
import com.smsc.headend.module.data.enums.OperationGroup;
import com.smsc.headend.module.data.enums.OperationName;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskExecutorException;
import com.smsc.headend.task.engine.executor.TaskExecutor;
import com.smsc.headend.task.engine.facade.DataItemFacade;
import com.smsc.headend.task.engine.handler.impl.deviceconfig.SetLimiterHandler;
import com.smsc.headend.module.task.dto.AtomicTaskState;
import com.smsc.headend.task.engine.service.ConnectorService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.task.TaskPacketService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

/**
 * @author wxh
 * @date 2020/12/23 15:18
 */
@Slf4j
public class SetLoadLimitExecutor implements TaskExecutor {
    @Autowired
    TaskPacketService packetService;
    @Autowired
    ConnectorService connectorService;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    FeignAssetManagementService assetManagementRemoteService;
    @Autowired
    DataItemFacade dataItemFacade;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    AssetService assetService;

    @Override
    public AtomicTaskState execute(Task task, AtomicTask atomicTask) throws UDISTaskExecutorException {
        log.info("execute step set loadLimit executor");
        DeviceOperation dictionary = assetService.getDictionary(atomicTask.getOperationId(), DictionaryType.Operation.getType());
        Meter meter = getMeter(task, assetService);
        if (task.getMeterId() != null && meter == null) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.METER_NOT_EXISTS.toString());
        }
        ComConfigDMO comConfigDMO = assetService.getComConfigById(task.getComId());
//        String interfaceType = comConfigDMO.getInterfaceType();
//        Boolean isShortName = comConfigDMO.getIsShortName();
        MeterConfigDMO meterConfigDMO = null;
        if (task.getMeterId() != null) {
            meterConfigDMO = assetService.getMeterConfigById(task.getMeterId());
        }
        List<DataItemDTO> dataItemDTOList = dataItemFacade.getDataItemsByOperation(meter, null, dictionary, getParameterMap(task, atomicTask), comConfigDMO.getModelId());
        if (CollectionUtils.isEmpty(dataItemDTOList)) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.DATAITEM_NOT_EXISTS.toString());
        }
        DeviceSAPandKeys deviceKeys = assetManagementRemoteService.getMeterCommunicationKey(meter.getMeterId(), dataItemDTOList.get(0).getRoleId());

        Map<String, Object> paramMap = this.getParameterMap(task, atomicTask);
        if (!paramMap.containsKey("loadLimitDataDto")) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.NO_LOAD_LIMIT_SET_DATA_TASK.toString());
        }
        LoadLimitDataDto loadLimitDataDto = JSONUtil.toBean((String)paramMap.get("loadLimitDataDto"), LoadLimitDataDto.class);
        log.info("loadLimitDataDto {}", loadLimitDataDto);

        byte[] packets = getPacketByOperationGroup(atomicTask, meterConfigDMO, dataItemDTOList, meter, deviceKeys, loadLimitDataDto);
        if (packets == null) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.PACKETS_IS_NULL.toString());
        }
        log.info(ByteUtils.byteToHexString(packets));
        connectorService.sendPacket(task, atomicTask, dataItemDTOList.get(0).getRoleId(), task.getIsODR(), packets);

        Class handlerClass = getHandlerClass(atomicTask);
        return createAtomicState(handlerClass);
    }

    @Override
    public Boolean handleFailure(Task task, AtomicTask atomicTask, String message) {
        if (atomicTask.getGroupId() == OperationGroup.SetLoadLimit.getId().intValue()) {
            String stepOperationName = OperationName.forId(atomicTask.getOperationId()).getName();
            taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), stepOperationName, String.format("step %s failed, [%s]", stepOperationName, message), 3600l, false);
        }
        return true;
    }


    private byte[] getPacketByOperationGroup(AtomicTask atomicTask, MeterConfigDMO meterConfigDMO, List<DataItemDTO> dataItemDTOList, Meter meter, DeviceSAPandKeys deviceSAPandKeys, LoadLimitDataDto dataDto) throws UDISTaskExecutorException {
        byte[] packet = null;
        OperationGroup operationGp = OperationGroup.forId(atomicTask.getGroupId().intValue());
        switch (operationGp) {
            case SetLoadLimit:
                packet = setLimiterPacket(atomicTask, meterConfigDMO, meter, dataItemDTOList, deviceSAPandKeys, dataDto);
                break;
            default:
                log.info("no implementation of this type {}", operationGp);
        }
        return packet;
    }

    private byte[] setLimiterPacket(AtomicTask atomicTask, MeterConfigDMO meterConfigDMO, Meter meter, List<DataItemDTO> dataItemDTOList, DeviceSAPandKeys deviceSAPandKeys, LoadLimitDataDto dataDto) throws UDISTaskExecutorException {
        byte[] packet = null;
        OperationName opName = OperationName.forId(atomicTask.getOperationId());
        switch (opName) {
            case SetThresholdNormal:
                Double normalVal = getConvertValue(meter, dataItemDTOList, dataDto.getNormal());
                dataDto.setNormal(normalVal);
                packet = packetService.setLimiterPacket(dataItemDTOList.get(0), meter, meterConfigDMO, deviceSAPandKeys, dataDto);
                break;
            case SetThresholdEmergency:
                Double emergencyVal = getConvertValue(meter, dataItemDTOList, dataDto.getEmergency());
                dataDto.setEmergency(emergencyVal);
                packet = packetService.setLimiterPacket(dataItemDTOList.get(0), meter, meterConfigDMO, deviceSAPandKeys, dataDto);
                break;
            case SetMonitorValue:
            case SetMinOverThresholdDuration:
            case SetMinUnderThresholdDuration:
            case SetEmergencyProfile:
            case SetEmergencyProfileGroupIDList:
            case SetLoadLimitActions:
                packet = packetService.setLimiterPacket(dataItemDTOList.get(0), meter, meterConfigDMO, deviceSAPandKeys, dataDto);
                break;
        }
        return packet;
    }

    private Double getConvertValue(Meter meter, List<DataItemDTO> dataItemDTOList, Double value) throws UDISTaskExecutorException{
        ScalerUnit scalerUnit = assetManagementRemoteService.getScalerUnitByModelIdSubDataItemId(meter.getModelId(), dataItemDTOList.get(0).getSubDataItemId());
        if(scalerUnit != null) {
            BigDecimal val = new BigDecimal(Convert.toStr(value)).divide(BigDecimal.valueOf(Math.pow(10, scalerUnit.getScaler())));
            if(new BigDecimal(val.longValue()).compareTo(val)!=0){
                // 转换后不能是小数下发
                log.info("invalid value after convert {} {}", value, scalerUnit.getScaler());
                throw new UDISTaskExecutorException(TaskExecuteErrorCode.DATA_CONVERT_ERROR.toString());
            }
            return val.doubleValue();
        } else {
            // set不能是小数下发到电表
            if (NumberUtil.isDouble(Convert.toStr(value))) {
                throw new UDISTaskExecutorException(TaskExecuteErrorCode.DATA_CONVERT_ERROR.toString());
            }
        }
        return value;
    }

    private Class getHandlerClass(AtomicTask atomicTask) throws UDISTaskExecutorException {
        Class cla = null;
        switch (OperationGroup.forId(atomicTask.getGroupId().intValue())) {
            case SetLoadLimit:
                cla = SetLimiterHandler.class;
                break;
            default:
                throw new UDISTaskExecutorException(TaskExecuteErrorCode.UNEXPECTED_VALUE.toString() + atomicTask.getGroupId().intValue());
        }
        return cla;
    }

}
